package com.bsj.power.sdk.config.hksdk;

import cn.hutool.core.io.FileUtil;
import cn.hutool.poi.excel.ExcelReader;
import cn.hutool.poi.excel.ExcelUtil;
import com.bsj.power.common.config.global.JsonResult;
import com.bsj.power.common.def.mq.sdk.SdkDevice;
import com.bsj.power.common.def.mq.sdk.SdkLoginDevInfo;
import com.bsj.power.common.def.mq.sdk.face.SdkFaceLoginDevInfo;
import com.bsj.power.common.util.ExceptionUtil;
import com.bsj.power.sdk.cache.ChildDeviceCache;
import com.bsj.power.sdk.cache.ChildFaceDeviceCache;
import com.bsj.power.sdk.cache.VideoChCache;
import com.bsj.power.sdk.config.hksdk.Common.osSelect;
import com.bsj.power.sdk.config.hksdk.alarm.AlarmSet;
import com.bsj.power.sdk.config.hksdk.alarm.FMSGCallBack;
import com.bsj.power.sdk.config.hksdk.alarm.FMSGCallBack_V31;
import com.bsj.power.sdk.config.hksdk.constant.HKSdkConstant;
import com.sun.jna.Native;
import com.sun.jna.Pointer;
import com.sun.jna.ptr.IntByReference;
import lombok.extern.slf4j.Slf4j;

import java.io.FileNotFoundException;
import java.io.FileOutputStream;
import java.io.IOException;
import java.nio.ByteBuffer;
import java.util.List;
import java.util.Map;
import java.util.concurrent.ConcurrentHashMap;

/**
 * @author yinhao
 * @version 1.0
 * @description 海康SDK初始化
 * @date 2024/8/22
 */
@Slf4j
public class HKSDKClientManage {

    public static HCNetSDK hCNetSDK = null;
    public static PlayCtrl playControl = null;
    static IntByReference m_lPort = new IntByReference(-1);
    //预览回调函数实现
    public static FRealDataCallBack fRealDataCallBack = null;
    public static FExceptionCallBack_Imp fExceptionCallBack;
    static FMSGCallBack_V31 fMSFCallBack_V31 = null;
    static FMSGCallBack fMSFCallBack = null;
    /**
     * 用户句柄
     */
//    public static int lUserID = -1;
    /**
     * 预览通道号
     */
//    public static int lDChannel;

    public static Map<String, SdkLoginDevInfo> crvSdkDev = new ConcurrentHashMap<>();

    static class FExceptionCallBack_Imp implements HCNetSDK.FExceptionCallBack {
        @Override
        public void invoke(int dwType, int lUserID, int lHandle, Pointer pUser) {
            log.error("异常事件类型: {}", dwType);
            return;
        }
    }


    public static boolean init() throws Exception {
        if (hCNetSDK == null && playControl == null) {
            if (!createSDKInstance(HKSdkConstant.path)) {
                log.error("Load SDK fail");
                return false;
            }
            if (!createPlayInstance(HKSdkConstant.path)) {
                log.error("Load PlayCtrl fail");
                return false;
            }
        }
        //linux系统建议调用以下接口加载组件库
        if (osSelect.isLinux()) {
            HCNetSDK.BYTE_ARRAY ptrByteArray1 = new HCNetSDK.BYTE_ARRAY(256);
            HCNetSDK.BYTE_ARRAY ptrByteArray2 = new HCNetSDK.BYTE_ARRAY(256);
            //这里是库的绝对路径，请根据实际情况修改，注意改路径必须有访问权限
            String strPath1 = HKSdkConstant.path + "/hksdk/lib/libcrypto.so.1.1";
            String strPath2 = HKSdkConstant.path + "/hksdk/lib/libssl.so.1.1";

            System.arraycopy(strPath1.getBytes(), 0, ptrByteArray1.byValue, 0, strPath1.length());
            ptrByteArray1.write();
            hCNetSDK.NET_DVR_SetSDKInitCfg(3, ptrByteArray1.getPointer());

            System.arraycopy(strPath2.getBytes(), 0, ptrByteArray2.byValue, 0, strPath2.length());
            ptrByteArray2.write();
            hCNetSDK.NET_DVR_SetSDKInitCfg(4, ptrByteArray2.getPointer());

            String strPathCom = HKSdkConstant.path + "/hksdk/lib/";
//            String strPathCom = "/opt/alarm/lib/";
            HCNetSDK.NET_DVR_LOCAL_SDK_PATH struComPath = new HCNetSDK.NET_DVR_LOCAL_SDK_PATH();
            System.arraycopy(strPathCom.getBytes(), 0, struComPath.sPath, 0, strPathCom.length());
            struComPath.write();
            hCNetSDK.NET_DVR_SetSDKInitCfg(2, struComPath.getPointer());
        }

        //SDK初始化，一个程序只需要调用一次
        boolean initSuc = hCNetSDK.NET_DVR_Init();

        //异常消息回调
        if (fExceptionCallBack == null) {
            fExceptionCallBack = new FExceptionCallBack_Imp();
        }

        //预览回调函数定义必须是全局的
        if (fRealDataCallBack == null) {
            fRealDataCallBack = new FRealDataCallBack();
        }
        Pointer pUser = null;
        if (!hCNetSDK.NET_DVR_SetExceptionCallBack_V30(0, 0, fExceptionCallBack, pUser)) {
            return false;
        }
        log.info("HKSDK设置异常消息回调成功");

        //启动SDK写日志
        hCNetSDK.NET_DVR_SetLogToFile(3, "./sdkLog", false);

        //设置报警回调函数
        if (fMSFCallBack_V31 == null) {
            fMSFCallBack_V31 = new FMSGCallBack_V31();
            String UserData = "UserData";
            HCNetSDK.BYTE_ARRAY UserDataByte = new HCNetSDK.BYTE_ARRAY(25);
            UserDataByte.read();
            UserDataByte.byValue = UserData.getBytes();
            UserDataByte.write();
            Pointer pUserDataByte = UserDataByte.getPointer();

            if (!hCNetSDK.NET_DVR_SetDVRMessageCallBack_V31(fMSFCallBack_V31, pUserDataByte)) {
                log.info("设置回调函数失败!");
                return false;
            } else {
                log.info("设置回调函数成功!");
            }
        }
        /** 设备上传的报警信息是COMM_VCA_ALARM(0x4993)类型，
         在SDK初始化之后增加调用NET_DVR_SetSDKLocalCfg(enumType为NET_DVR_LOCAL_CFG_TYPE_GENERAL)设置通用参数NET_DVR_LOCAL_GENERAL_CFG的byAlarmJsonPictureSeparate为1，
         将Json数据和图片数据分离上传，这样设置之后，报警布防回调函数里面接收到的报警信息类型为COMM_ISAPI_ALARM(0x6009)，
         报警信息结构体为NET_DVR_ALARM_ISAPI_INFO（与设备无关，SDK封装的数据结构），更便于解析。*/

        HCNetSDK.NET_DVR_LOCAL_GENERAL_CFG struNET_DVR_LOCAL_GENERAL_CFG = new HCNetSDK.NET_DVR_LOCAL_GENERAL_CFG();
        struNET_DVR_LOCAL_GENERAL_CFG.byAlarmJsonPictureSeparate = 1;   //设置JSON透传报警数据和图片分离
        struNET_DVR_LOCAL_GENERAL_CFG.write();
        Pointer pStrNET_DVR_LOCAL_GENERAL_CFG = struNET_DVR_LOCAL_GENERAL_CFG.getPointer();
        hCNetSDK.NET_DVR_SetSDKLocalCfg(17, pStrNET_DVR_LOCAL_GENERAL_CFG);


        //先登录对应的CVR
        String[] splitAuth = HKSdkConstant.auth.split(";");
        for (String authStr : splitAuth) {
            String[] authArr = authStr.split(",");
            if (authArr.length < 5) {
                continue;
            }
            SdkLoginDevInfo crvDevInfo = login_child_V40(authArr[0], (short) Integer.parseInt(authArr[1]),
                    authArr[2], authArr[3]);
            if (crvDevInfo == null) {
                continue;
            }
            crvDevInfo.setDeviceName(authArr[4]);
            crvSdkDev.put(authArr[4], crvDevInfo);
        }
//        login_V40(HKSdkConstant.ip, HKSdkConstant.port.shortValue(), HKSdkConstant.user, HKSdkConstant.psw);
        //再登录全部的设备
        List<SdkDevice> allSdkDev = VideoChCache.getAllSdkDev();
        allSdkDev.stream().forEach(item -> {
            SdkLoginDevInfo sdkLoginDevInfo = login_child_V40(item.getIp(), item.getPort(), item.getUser(), item.getPsw());
            if (sdkLoginDevInfo != null) {
                sdkLoginDevInfo.setId(item.getDevice().getId());
                sdkLoginDevInfo.setLAlarmHandle(-1);
                sdkLoginDevInfo.setLAlarmHandle_V50(-1);
                sdkLoginDevInfo.setLListenHandle(-1);
                sdkLoginDevInfo.setDeviceName(item.getDevice().getDeviceId());
                ChildDeviceCache.addInfo(sdkLoginDevInfo.getUserId(), sdkLoginDevInfo);
                //登录成后，直接开启设备布防
                AlarmSet.arrange(sdkLoginDevInfo, new JsonResult());
            }
        });
        log.info("所有摄像头登录信息：{}", ChildDeviceCache.getAllDevList());
        //登录面板机
        ChildFaceDeviceCache childFaceDeviceCache = new ChildFaceDeviceCache();
        childFaceDeviceCache.start();


        //注释掉的代码也可以参考，去掉注释可以运行
        //获取IP通道
//        VideoDemo.getIPChannelInfo(lUserID);

        //实时取流
//        VideoDemo.realPlay(lUserID,1);

        //按时间回放和下载
//          new VideoDemo().playBackBytime(lUserID,34);

        //按时间下载录像
//        new VideoDemo().dowmloadRecordByTime(lUserID);

        //按时间回放和下载录像，需要等待回放和下载完成后调用注销和释放接口
//        while (true)
//        {
//
//        }

        //按录像文件回放和下载
//        VideoDemo.downloadRecordByFile(lUserID, 34);//录像文件查找下载
//
//        VideoDemo.playBackByfile(lUserID,33);
//        try {
//            Thread.sleep(50000);
//        } catch (InterruptedException e) {
//            e.printStackTrace();
//        }

        //退出程序时调用，每一台设备分别注销
//        if (hCNetSDK.NET_DVR_Logout(lUserID)) {
//            System.out.println("注销成功");
//        }

        //SDK反初始化，释放资源，只需要退出时调用一次
//        hCNetSDK.NET_DVR_Cleanup();
        return true;
    }

    /**
     * 关闭时释放资源
     */
    public static void logout() {
        crvSdkDev.forEach((key, value) -> {
            if (hCNetSDK.NET_DVR_Logout(value.getUserId())) {
                log.info("HKSDK-CVR注销成功,lUserID: {}, 设备号： {}", value.getUserId(), value.getDeviceName());
            }
        });
        List<SdkLoginDevInfo> allDevList = ChildDeviceCache.getAllDevList();
        allDevList.forEach(item -> {
            if (hCNetSDK.NET_DVR_Logout(item.getUserId())) {
                log.info("HKSDK-子设备注销成功,lUserID: {}, 设备号： {}", item.getUserId(), item.getDeviceName());
            }
        });
        List<SdkFaceLoginDevInfo> allFaceDevList = ChildFaceDeviceCache.getAllDevList();
        allFaceDevList.forEach(item -> {
            if (hCNetSDK.NET_DVR_Logout(item.getUserId())) {
                log.info("HKSDK-面板机注销成功,lUserID: {}", item.getUserId());
            }
        });
        //SDK反初始化，释放资源，只需要退出时调用一次
        hCNetSDK.NET_DVR_Cleanup();
    }

    /**
     * 动态加载库文件
     *
     * @return
     */
    private static boolean createSDKInstance(String dllPath) {
        if (hCNetSDK == null) {
            synchronized (HCNetSDK.class) {
                String strDllPath = "";
                try {
                    if (osSelect.isWindows()) {
                        //win系统加载库路径
                        strDllPath = dllPath + "\\hksdk\\lib\\HCNetSDK.dll";
                    } else if (osSelect.isLinux()) {
                        //Linux系统加载库路径
                        strDllPath = dllPath + "/hksdk/lib/libhcnetsdk.so";
                    }
                    hCNetSDK = (HCNetSDK) Native.loadLibrary(strDllPath, HCNetSDK.class);
                } catch (Exception ex) {
                    log.error("loadLibrary: {}, Error: {}", strDllPath, ExceptionUtil.getStackStr(ex));
                    return false;
                }
            }
        }
        return true;
    }

    /**
     * 播放库加载
     *
     * @return
     */
    private static boolean createPlayInstance(String dllPath) {
        if (playControl == null) {
            synchronized (PlayCtrl.class) {
                String strPlayPath = "";
                try {
                    if (osSelect.isWindows()) {
                        //win系统加载库路径
                        strPlayPath = dllPath + "\\hksdk\\lib\\PlayCtrl.dll";
                    } else if (osSelect.isLinux()) {
                        //Linux系统加载库路径
                        strPlayPath = dllPath + "/hksdk/lib/libPlayCtrl.so";
                    }
                    playControl = (PlayCtrl) Native.loadLibrary(strPlayPath, PlayCtrl.class);

                } catch (Exception ex) {
                    log.error("loadLibrary: {}, Error: {}", strPlayPath, ExceptionUtil.getStackStr(ex));
                    return false;
                }
            }
        }
        return true;
    }

    /**
     * 设备登录V40 与V30功能一致
     *
     * @param ip   设备IP
     * @param port SDK端口，默认设备的8000端口
     * @param user 设备用户名
     * @param psw  设备密码
     */
//    public static void login_V40(String ip, short port, String user, String psw) {
//        //注册
//        //设备登录信息
//        HCNetSDK.NET_DVR_USER_LOGIN_INFO m_strLoginInfo = new HCNetSDK.NET_DVR_USER_LOGIN_INFO();
//        //设备信息
//        HCNetSDK.NET_DVR_DEVICEINFO_V40 m_strDeviceInfo = new HCNetSDK.NET_DVR_DEVICEINFO_V40();
//
//        //设备ip地址
//        String m_sDeviceIP = ip;
//        m_strLoginInfo.sDeviceAddress = new byte[HCNetSDK.NET_DVR_DEV_ADDRESS_MAX_LEN];
//        System.arraycopy(m_sDeviceIP.getBytes(), 0, m_strLoginInfo.sDeviceAddress, 0, m_sDeviceIP.length());
//
//        //设备用户名
//        String m_sUsername = user;
//        m_strLoginInfo.sUserName = new byte[HCNetSDK.NET_DVR_LOGIN_USERNAME_MAX_LEN];
//        System.arraycopy(m_sUsername.getBytes(), 0, m_strLoginInfo.sUserName, 0, m_sUsername.length());
//
//        //设备密码
//        String m_sPassword = psw;
//        m_strLoginInfo.sPassword = new byte[HCNetSDK.NET_DVR_LOGIN_PASSWD_MAX_LEN];
//        System.arraycopy(m_sPassword.getBytes(), 0, m_strLoginInfo.sPassword, 0, m_sPassword.length());
//
//        m_strLoginInfo.wPort = port;
//        //是否异步登录：0- 否，1- 是
//        m_strLoginInfo.bUseAsynLogin = false;
//        //0- SDK私有协议，1- ISAPI协议
//        m_strLoginInfo.byLoginMode = 0;
//        m_strLoginInfo.write();
//
//        lUserID = hCNetSDK.NET_DVR_Login_V40(m_strLoginInfo, m_strDeviceInfo);
//        if (lUserID == -1) {
//            log.warn("登录失败，错误码为: {}", hCNetSDK.NET_DVR_GetLastError());
//            return;
//        } else {
//            log.info("设备登录成功！ IP: {}", ip);
//            //相机一般只有一个通道号，热成像相机有2个通道号，通道号为1或1,2
//            //byStartDChan为IP通道起始通道号, 预览回放NVR的IP通道时需要根据起始通道号进行取值
//            if ((int) m_strDeviceInfo.struDeviceV30.byStartDChan == 1 && (int) m_strDeviceInfo.struDeviceV30.byStartDChan == 33) {
//                //byStartDChan为IP通道起始通道号, 预览回放NVR的IP通道时需要根据起始通道号进行取值,NVR起始通道号一般是33或者1开始
//                lDChannel = m_strDeviceInfo.struDeviceV30.byStartDChan;
//                log.info("预览起始通道号： {}", lDChannel);
//            }
//            return;
//        }
//    }

    /**
     * 子设备登录
     *
     * @param ip
     * @param port
     * @param user
     * @param psw
     */
    public static SdkLoginDevInfo login_child_V40(String ip, short port, String user, String psw) {
        //注册
        //设备登录信息
        HCNetSDK.NET_DVR_USER_LOGIN_INFO m_strLoginInfo = new HCNetSDK.NET_DVR_USER_LOGIN_INFO();
        //设备信息
        HCNetSDK.NET_DVR_DEVICEINFO_V40 m_strDeviceInfo = new HCNetSDK.NET_DVR_DEVICEINFO_V40();

        //设备ip地址
        String m_sDeviceIP = ip;
        m_strLoginInfo.sDeviceAddress = new byte[HCNetSDK.NET_DVR_DEV_ADDRESS_MAX_LEN];
        System.arraycopy(m_sDeviceIP.getBytes(), 0, m_strLoginInfo.sDeviceAddress, 0, m_sDeviceIP.length());

        //设备用户名
        String m_sUsername = user;
        m_strLoginInfo.sUserName = new byte[HCNetSDK.NET_DVR_LOGIN_USERNAME_MAX_LEN];
        System.arraycopy(m_sUsername.getBytes(), 0, m_strLoginInfo.sUserName, 0, m_sUsername.length());

        //设备密码
        String m_sPassword = psw;
        m_strLoginInfo.sPassword = new byte[HCNetSDK.NET_DVR_LOGIN_PASSWD_MAX_LEN];
        System.arraycopy(m_sPassword.getBytes(), 0, m_strLoginInfo.sPassword, 0, m_sPassword.length());

        m_strLoginInfo.wPort = port;
        //是否异步登录：0- 否，1- 是
        m_strLoginInfo.bUseAsynLogin = false;
        //0- SDK私有协议，1- ISAPI协议
        m_strLoginInfo.byLoginMode = 0;
        m_strLoginInfo.write();
        int userID = -1;
        int idChannel = -1;

        userID = hCNetSDK.NET_DVR_Login_V40(m_strLoginInfo, m_strDeviceInfo);
        if (userID == -1) {
            log.warn("登录失败，错误码为: {}", hCNetSDK.NET_DVR_GetLastError());
            return null;
        } else {
            log.info("设备登录成功！ IP: {}, userID:{}", ip, userID);
            //相机一般只有一个通道号，热成像相机有2个通道号，通道号为1或1,2
            //byStartDChan为IP通道起始通道号, 预览回放NVR的IP通道时需要根据起始通道号进行取值
            if ((int) m_strDeviceInfo.struDeviceV30.byStartDChan == 1 && (int) m_strDeviceInfo.struDeviceV30.byStartDChan == 33) {
                //byStartDChan为IP通道起始通道号, 预览回放NVR的IP通道时需要根据起始通道号进行取值,NVR起始通道号一般是33或者1开始
                idChannel = m_strDeviceInfo.struDeviceV30.byStartDChan;
                log.info("预览起始通道号： {}", idChannel);
            }
            return SdkLoginDevInfo.builder().userId(userID).idChannel(idChannel).build();
        }
    }

    static class FRealDataCallBack implements HCNetSDK.FRealDataCallBack_V30 {
        //预览回调
        @Override
        public void invoke(int lRealHandle, int dwDataType, Pointer pBuffer, int dwBufSize, Pointer pUser) {
//            if (Count == 100) {//降低打印频率
//                log.info("码流数据回调...dwBufSize= {}", dwBufSize);
//                Count = 0;
//            }
//            Count++;
            //播放库解码
            switch (dwDataType) {
                case HCNetSDK.NET_DVR_SYSHEAD: //系统头
                    //获取播放库未使用的通道号
                    if (!playControl.PlayM4_GetPort(m_lPort)) {
                        break;
                    }
                    if (dwBufSize > 0) {
                        //设置实时流播放模式
                        if (!playControl.PlayM4_SetStreamOpenMode(m_lPort.getValue(), PlayCtrl.STREAME_REALTIME)) {
                            break;
                        }
                        //打开流接口
                        if (!playControl.PlayM4_OpenStream(m_lPort.getValue(), pBuffer, dwBufSize, 1024 * 1024)) {
                            break;
                        }
                        //播放开始
                        if (!playControl.PlayM4_Play(m_lPort.getValue(), null)) {
                            break;
                        }

                    }
                case HCNetSDK.NET_DVR_STREAMDATA:   //码流数据
                    if ((dwBufSize > 0) && (m_lPort.getValue() != -1)) {
                        //输入流数据
                        if (!playControl.PlayM4_InputData(m_lPort.getValue(), pBuffer, dwBufSize)) {
                            break;
                        }
                    }
            }
        }
    }

    /**
     * 播放库抓图
     *
     * @param lPlay    设备登录ID
     * @param path     文件存储路径前缀
     * @param filePath 数据保存路径
     */
    public static void getPicbyPlayCtrl(int lPlay, String path, String filePath) {

        //取流成功后，延时一段时间保证播放库解码开始
        try {
            Thread.sleep(3000);
        } catch (InterruptedException e) {
            e.printStackTrace();
        }
        IntByReference pWidth = new IntByReference(0);
        IntByReference pHieght = new IntByReference(0);
        boolean bFlag = playControl.PlayM4_GetPictureSize(m_lPort.getValue(), pWidth, pHieght);
        if (!bFlag) {
            log.warn("获取失败： {}", playControl.PlayM4_GetLastError(m_lPort.getValue()));
        }
        log.info("宽度: {}, 高度: {}", pWidth.getValue(), pHieght.getValue());
        IntByReference RealPicSize = new IntByReference(0);
        int picsize = pWidth.getValue() * pHieght.getValue() * 5;
        HCNetSDK.BYTE_ARRAY picByte = new HCNetSDK.BYTE_ARRAY(picsize);
        picByte.write();
        Pointer pByte = picByte.getPointer();
        boolean b_GetPic = playControl.PlayM4_GetJPEG(m_lPort.getValue(), pByte, picsize, RealPicSize);
        if (!b_GetPic) {
            log.info("抓图失败： {}", playControl.PlayM4_GetLastError(m_lPort.getValue()));
        }
        picByte.read();
        FileOutputStream fout = null;
        try {
            fout = new FileOutputStream(path + filePath);
            //将字节写入文件
            long offset = 0;
            ByteBuffer buffers = pByte.getByteBuffer(offset, RealPicSize.getValue());
            byte[] bytes = new byte[RealPicSize.getValue()];
            buffers.rewind();
            buffers.get(bytes);
            fout.write(bytes);
            fout.close();
        } catch (FileNotFoundException e) {
            // TODO Auto-generated catch block
            e.printStackTrace();
        } catch (IOException e) {
            // TODO Auto-generated catch block
            e.printStackTrace();
        }
        log.info("抓图成功: {}", path + filePath);

//        Boolean bStopSaveVideo = hCNetSDK.NET_DVR_StopSaveRealData(lPlay);
//        if (bStopSaveVideo == false) {
//            int iErr = hCNetSDK.NET_DVR_GetLastError();
//            log.warn("设备预览停止流失败: {}", iErr);
//            return;
//        }
//        log.info("设备预览停止流成功");
        if (lPlay >= 0) {
            if (hCNetSDK.NET_DVR_StopRealPlay(lPlay)) {
                log.info("设备停止预览成功");
                return;
            }
        }
    }

}
